<?php
/**
 * Zend Framework
 *
 * LICENSE
 *
 * This source file is subject to the new BSD license that is bundled
 * with this package in the file LICENSE.txt.
 * It is also available through the world-wide-web at this URL:
 * http://framework.zend.com/license/new-bsd
 * If you did not receive a copy of the license and are unable to
 * obtain it through the world-wide-web, please send an email
 * to license@zend.com so we can send you a copy immediately.
 *
 * @category   Zend
 * @package    Zend_Calendar
 * @copyright  Copyright (c) 2005-2009 Zend Technologies USA Inc. (http://www.zend.com)
 * @license    http://framework.zend.com/license/new-bsd     New BSD License
 * @version    $Id$
 */

/**
 * Zend_Calendar
 *
 * @category   Zend
 * @package    Zend_Calendar
 * @copyright  Copyright (c) 2005-2009 Zend Technologies USA Inc. (http://www.zend.com)
 * @license    http://framework.zend.com/license/new-bsd     New BSD License
 */
class Zend_Calendar
{
	const SUNDAY = 0;
	const MONDAY = 1;
	const TUESDAY = 2;
	const WEDNESDAY = 3;
	const THURSDAY = 4;
	const FRIDAY = 5;
	const SATURDAY = 6;
	
	/**
	 * @var int Starting day of the week.
	 */
	protected $_startOfWeek = self::SUNDAY;
	
	/**
	 * @var Zend_Date
	 */
	protected $_date = null;
	
	/**
	 * @var array Cached list of day names from localization list.
	 */
	protected $_dayNames = array();
	
	/**
	 * @var array Cached list of day names from localization list.
	 */
	protected $_monthNames = array();
	
	/**
	 * @var array Cached list of days.
	 */
	protected $_days = array();
	
	/**
	 * @var array Cached list of weeks.
	 */
	protected $_weeks = array();
	
	/**
	 * Calendar constructor.
	 * Available options:
	 * 		* day - day to set the calendar to.
	 * 		* month - month to set the calendar to.
	 * 		* year - year to set the calendar to.
	 * 		* cache - instance of Zend_Cache.
	 * @param array params
	 */
	public function __construct(array $options = array())
	{
		$date = array('day' => date('d'), 'month' => date('m'), 'year' => date('Y'));
		$params = Zend_Controller_Front::getInstance()->getRequest()->getParams();
		
		if (isset($params['day'])) {
			$date['day'] = $params['day'];
		} else if (isset($options['day'])) {
			$date['day'] = $options['day'];
		}
		
		if (isset($params['month'])) {
			$date['month'] = $params['month'];
		} else if (isset($options['month'])) {
			$date['month'] = $options['month'];
		}
		
		if (isset($params['year'])) {
			$date['year'] = $params['year'];
		} else if (isset($options['year'])) {
			$date['year'] = $options['year'];
		}
		
		$this->setDate($date);
		
		if (isset($options['cache']) || Zend_Registry::isRegistered('Zend_Calendar_Cache')) {
			if (isset($options['cache'])) {
				$cache = $options['cache'];
			} else {
				$cache = Zend_Registry::get('Zend_Calendar_Cache');
			}
			
			if (!($cache instanceof Zend_Cache)) {
				throw new Zend_Calendar_Exception(
					'Zend_Calendar_Cache requires an instance of Zend_Cache.');
			}
			
			$this->_date->setOptions(array('cache' => Zend_Registry::get('Zend_Calendar_Cache')));
		}
		
		$this->init();
	}
	
	/**
	 * Serialize as string
	 *
	 * Proxies to {@link render()}.
	 *
	 * @return string
	 */
	public function __toString()
	{
		try {
			$return = $this->render();
			return $return;
		} catch (Exception $e) {
			trigger_error($e->getMessage(), E_USER_WARNING);
		}
		
		return '';
	}
	
	/**
	 * Method provided for children class initialization.
	 */
	public function init()
	{}
	
	/**
	 * Gets all days for the calendar.
	 */
	public function getDays()
	{
		return $this->_days;
	}
	
	/**
	 * Gets (or sets if doesn't exist) a day for the calendar. Each day
	 * can contain an unlimited number of events. Days are not created
	 * until needed for efficiency.
	 *
	 * @param array|Zend_Calendar_Day Parameters to specify the day.
	 */
	public function day($param)
	{
		if ($param instanceof Zend_Date) {
			$day = $param->get(Zend_Date::DAY);
			$month = $param->get(Zend_Date::MONTH);
			$year = $param->get(Zend_Date::YEAR);
		} else if (is_array($param)) {
			if (isset($param['day'])) {
				if (strlen($param['day']) < 2) {
					$day = '0' . $param['day'];
				} else {
					$day = $param['day'];
				}
			} else {
				$day = date('d');
			}
			
			if (isset($param['month'])) {
				if (strlen($param['month']) < 2) {
					$month = '0' . $param['month'];
				} else {
					$month = $param['month'];
				}
			} else {
				$month = date('m');
			}
			
			if (isset($param['year'])) {
				$year = date('Y');
				$year = substr($year, 0, 2) . $param['year'];
				
				if (strlen($param['year']) == 2) {
					$year = date('Y');
					$year = substr($month, 0, 2) . $param['year'];
				} else {
					$year = $param['year'];
				}
			} else {
				$year = date('Y');
			}
		
		} else {
			throw new Zend_Calendar_Exception(
				'Day must be an array containing the date or an instance of Zend_Date.');
		}
		
		if (!isset($this->_days[$year][$month][$day])) {
			$this->_days[$year][$month][$day] = new Zend_Calendar_Day("{$day}.{$month}.{$year}", 
				'dd.mm.yyyy');
		}
		
		return $this->_days[$year][$month][$day];
	}
	
	/**
	 * Retrieve formatted date from calendar's Zend_Date object.
	 *
	 * @return mixed
	 */
	public function get($format)
	{
		return $this->_date->get($format);
	}
	
	/**
	 * Create an event for the calendar using the specified event type.
	 *
	 * @param string|Zend_Calendar_Event_Source_* Stringed name of event source or event source object.
	 * @param variable data
	 * @param array params
	 * @return Zend_Calendar
	 */
	public function addEvent($source, $data = null, array $params = array())
	{
		if (is_string($source)) {
			$source = 'Zend_Calendar_Event_Source_' . ucfirst($source);
			
			Zend_Loader::loadClass($source);
			
			$source = new $source($data, $params);
			$this->_parseEvents($source->getEvents());
		} else if ($source instanceof Zend_Calendar_Event_Source_Abstract) {
			$this->_parseEvents($source->getEvents());
		} else {
			throw new Zend_Calendar_Exception('Unknown calendar event source.');
		}
		
		return $this;
	}
	
	/**
	 * Render block
	 *
	 * @return string
	 */
	public function render($options = array(), $adapterName = Zend_Calendar_Render::DEFAULT_ADAPTER)
	{
		return Zend_Calendar_Render::factory($this, $options, $adapterName)->render();
	}
	
	/**
	 * Retrieve the calendar's Zend_Date object.
	 */
	public function getDate()
	{
		return $this->_date;
	}
	
	/**
	 * Sets the calendars date using Zend_Date.
	 *
	 * @param mixed an array containing the date or an instance of Zend_Date.
	 * @return self
	 */
	public function setDate($date = null)
	{
		if (is_array($date)) {
			if (isset($date['day'])) {
				$cleanDate['day'] = $date['day'];
			} else {
				$cleanDate['day'] = date('d');
			}
			
			if (isset($date['month'])) {
				$cleanDate['month'] = $date['month'];
			} else {
				$cleanDate['month'] = date('m');
			}
			
			if (isset($date['year'])) {
				$cleanDate['year'] = $date['year'];
			} else {
				$cleanDate['year'] = date('Y');
			}
			
			$date = new Zend_Date();
			$date->setDay($cleanDate['day']);
			$date->setMonth($cleanDate['month']);
			$date->setYear($cleanDate['year']);
		}
		
		$this->_date = $date;
		return $this;
	}
	
	/**
	 * End of week based on the start of week.
	 * @return int end of week.
	 */
	public function getEndOfWeek()
	{
		if ($this->_startOfWeek == self::SUNDAY) {
			return self::SATURDAY;
		} else {
			return ($this->_startOfWeek - 1);
		}
	}
	
	/**
	 * Gets a list of years from the current year plus and minus the limit.
	 * This is useful for generating year selectors for the calendar view.
	 *
	 * @param int upper and lower year limit
	 * @param string format to return year in (default Zend_Date::YEAR)
	 */
	public function getYearList($limit = 5, $format = Zend_Date::YEAR)
	{
		$tmpDate = clone $this->_date;
		$tmpDate->subYear($limit);
		
		$yearList = array();
		for ($i = -($limit); $i <= $limit; $i++) {
			$yearList[] = $tmpDate->get($format);
			$tmpDate->addYear(1);
		}
		
		return $yearList;
	}
	
	/**
	 * Populates calendar's day names from translation list and caches them.
	 *
	 * @param array format of the day names.
	 * @return array Day names of calendar.
	 */
	public function getDayNames(array $format = array())
	{
		if (empty($this->_dayNames)) {
			$dayNames = Zend_Locale::getTranslationList('day', $this->_date->getLocale(), $format);
			
			$start = $this->_startOfWeek;
			while ($start--) {
				$tmp = array_shift($dayNames);
				$dayNames[] = $tmp;
			}
			
			$this->_dayNames = $dayNames;
		}
		
		return $this->_dayNames;
	}
	
	/**
	 * Populates calendar's month names from translation list and caches them.
	 *
	 * @param array format of the month names.
	 * @return array Month names of calendar.
	 */
	public function getMonthNames(array $format = array())
	{
		if (empty($this->_monthNames)) {
			$months = Zend_Locale::getTranslationList('month', $this->_date->getLocale(), $format);
			
			foreach ($months as $month) {
				$this->_monthNames[] = $month;
			}
		}
		
		return $this->_monthNames;
	}
	
	public function getStartOfWeek()
	{
		return $this->_startOfWeek;
	}
	
	public function setStartOfWeek($start)
	{
		$this->_startOfWeek = $start;
	}
	
	public function weeks()
	{
		if (empty($this->_weeks)) {
			$this->_populateWeeks();
		}
		
		return $this->_weeks;
	}
	
	/**
	 * Parse events and add them to the correct day.
	 *
	 * @param array Array of Zend_Calendar_Event(s).
	 */
	protected function _parseEvents(array $events = array())
	{
		$defaultDay = date('d');
		$defaultMonth = date('m');
		$defaultYear = date('Y');
		
		$date = array();
		foreach ($events as $event) {
			$date['day'] = $defaultDay;
			$date['month'] = $defaultMonth;
			$date['year'] = $defaultYear;
			
			if (null !== $event->getDay()) {
				$date['day'] = $event->getDay();
			}
			
			if (null !== $event->getMonth()) {
				$date['month'] = $event->getMonth();
			}
			
			if (null !== $event->getYear()) {
				$date['year'] = $event->getYear();
			}
			
			$this->day($date)->addEvent($event);
		}
	}
	
	/**
	 * Populates calendar weeks and caches.
	 */
	protected function _populateWeeks()
	{
		$date = clone $this->_date;
		$date->setDay(1);
		
		$daysInMonth = $date->get(Zend_Date::MONTH_DAYS);
		$firstDay = $date->get(Zend_Date::WEEKDAY_DIGIT);
		
		if ($firstDay < $this->_startOfWeek) {
			$date->subDay(7 - ($this->_startOfWeek - $firstDay));
		} else {
			$date->addDay($this->_startOfWeek - $firstDay);
		}
		
		$tempDays = $firstDay + $daysInMonth;
		$weeksInMonth = ceil($tempDays / 7);
		
		for ($j = 0; $j < $weeksInMonth; $j++) {
			for ($i = 0; $i < 7; $i++) {
				$this->_weeks[$j][] = $this->day($date);
				$date->addDay(1);
			}
		}
	}
}